package com.googlecode.textish.parser;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.googlecode.textish.format.IFormatter;
import com.googlecode.textish.tokenizer.CharToken;
import com.googlecode.textish.tokenizer.DocumentToken;
import com.googlecode.textish.tokenizer.IToken;
import com.googlecode.textish.tokenizer.LineToken;
import com.googlecode.textish.tokenizer.ParagraphToken;
import com.googlecode.textish.tokenizer.TextishLexicalAnalyzer;
import com.googlecode.textish.tokenizer.UnexpectedTokenException;
import com.googlecode.textish.tokenizer.WordToken;

/**
 * A parser traverses a {@link DocumentToken} and produces output.
 * 
 * @author cgdavies
 * 
 */
public class TextishParser {

	private List m_formatters;

	public TextishParser() {
		m_formatters = new ArrayList();
	}

	/**
	 * Parses a {@link DocumentToken} token tree and writes output to a
	 * {@link Writer}.
	 * 
	 * @param context the {@link CharSequence} the {@link DocumentToken} was
	 * built from
	 * @param document the {@link DocumentToken} produced by the lexical
	 * analyzer ({@link TextishLexicalAnalyzer})
	 * @param output the {@link Writer} to write to
	 * @throws IOException if any problems occurred trying to write to the
	 * {@link Writer}
	 */
	public void parse(CharSequence context, DocumentToken document, Writer output) throws IOException {
		ContainerTokenDecorator documentWrapper = new ContainerTokenDecorator(document);
		parseDocument(context, documentWrapper, output);
	}

	private void parseDocument(CharSequence context, ContainerTokenDecorator documentWrapper, Writer output)
			throws IOException {
		// Before.
		boolean onward = true;
		do {
			documentWrapper.setDirty(false);

			for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
				IFormatter formatter = (IFormatter) it.next();
				onward = formatter.beforeDocument(context, documentWrapper, output);
			}
		} while (onward && documentWrapper.isDirty());

		// Down.
		if (onward) {
			for (Iterator it = documentWrapper.getChildTokens().iterator(); it.hasNext();) {
				ParagraphToken paragraph = (ParagraphToken) it.next();
				ContainerTokenDecorator paragraphWrapper = new ContainerTokenDecorator(paragraph);

				parseParagraph(context, paragraphWrapper, output);
			}
		}
		else {
			// Output raw.
			output.append(context.subSequence(documentWrapper.getStartIndex(), documentWrapper.getEndIndex()));
		}

		// After.
		onward = true;
		for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
			IFormatter formatter = (IFormatter) it.next();
			onward = formatter.afterDocument(context, documentWrapper, output);
		}
	}

	private void parseParagraph(CharSequence context, ContainerTokenDecorator paragraphWrapper, Writer output)
			throws IOException {
		// Before.
		boolean onward = true;
		do {
			paragraphWrapper.setDirty(false);

			for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
				IFormatter formatter = (IFormatter) it.next();
				onward = formatter.beforeParagraph(context, paragraphWrapper, output);
			}
		} while (onward && paragraphWrapper.isDirty());

		// Down.
		if (onward) {
			for (Iterator it = paragraphWrapper.getChildTokens().iterator(); it.hasNext();) {
				LineToken line = (LineToken) it.next();
				ContainerTokenDecorator lineWrapper = new ContainerTokenDecorator(line);

				parseLine(context, lineWrapper, output);
			}
		}
		else {
			// Output raw.
			output.append(context.subSequence(paragraphWrapper.getStartIndex(), paragraphWrapper.getEndIndex()));
		}

		// After.
		onward = true;
		for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
			IFormatter formatter = (IFormatter) it.next();
			onward = formatter.afterParagraph(context, paragraphWrapper, output);
		}
	}

	private void parseLine(CharSequence context, ContainerTokenDecorator lineWrapper, Writer output) throws IOException {
		// Before.
		boolean onward = true;
		do {
			lineWrapper.setDirty(false);

			for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
				IFormatter formatter = (IFormatter) it.next();
				onward = formatter.beforeLine(context, lineWrapper, output);
			}
		} while (onward && lineWrapper.isDirty());

		// Down.
		if (onward) {
			for (Iterator it = lineWrapper.getChildTokens().iterator(); it.hasNext();) {
				IToken token = (IToken) it.next();

				if (token instanceof CharToken) {
					output.append(context.subSequence(token.getStartIndex(), token.getEndIndex()));
				}
				else if (token instanceof WordToken) {
					WordToken word = (WordToken) token;
					ContainerTokenDecorator wordWrapper = new ContainerTokenDecorator(word);

					parseWord(context, wordWrapper, output);
				}
				else
					throw new UnexpectedTokenException(token.getClass());
			}
		}
		else {
			// Output raw.
			output.append(context.subSequence(lineWrapper.getStartIndex(), lineWrapper.getEndIndex()));
		}

		// After.
		onward = true;
		for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
			IFormatter formatter = (IFormatter) it.next();
			onward = formatter.afterLine(context, lineWrapper, output);
		}
	}

	private void parseWord(CharSequence context, ContainerTokenDecorator wordWrapper, Writer output) throws IOException {
		// Before.
		boolean onward = true;
		do {
			wordWrapper.setDirty(false);

			for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
				IFormatter formatter = (IFormatter) it.next();
				onward = formatter.beforeWord(context, wordWrapper, output);
			}
		} while (onward && wordWrapper.isDirty());

		// Output raw.
		output.append(context.subSequence(wordWrapper.getStartIndex(), wordWrapper.getEndIndex()));

		// After.
		onward = true;
		for (Iterator it = m_formatters.iterator(); onward && it.hasNext();) {
			IFormatter formatter = (IFormatter) it.next();
			onward = formatter.afterWord(context, wordWrapper, output);
		}
	}

	public void setFormatters(List formatters) {
		m_formatters = formatters;
	}

	public List getFormatters() {
		return m_formatters;
	}

}
